/*************************************************************************************
 * Nombre del archivo: CPU.java
 * Universidad del Valle de Guatemala
 * Curso: Sistemas Operativos
 * @author martin
 *          Martín Guzmán 08041
 * Fecha: Febrero de 2010
 * Descripción:
*************************************************************************************/
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Queue;
import java.util.logging.Level;
import java.util.logging.Logger;
//para parsear el XML
import java.io.*;
import java.util.*;
import org.jdom.*;
import org.jdom.input.*;
import org.jdom.output.*;
//******************************************************************************
//estadísticas
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartUtilities;
import org.jfree.chart.JFreeChart;
import org.jfree.data.general.DefaultPieDataset;
import java.io.File;
//******************************************************************************

public class CPU extends Thread{

    public volatile static LinkedList<Interfaz_Cola<Proceso>> lista_colas = new LinkedList<Interfaz_Cola<Proceso>>();

    public volatile static LinkedList<Archivo> Archivos_PID = new LinkedList<Archivo>();//lista de archivos en uso

    public volatile static LinkedList<Proceso> Procesos_por_matar = new LinkedList<Proceso>();
    public volatile static LinkedList<Proceso> Waiting = new LinkedList<Proceso>();
    public volatile static LinkedList<Integer> Waiting_time = new LinkedList<Integer>();
    public volatile static LinkedList<Integer> Waiting_time_cola = new LinkedList<Integer>();
    public volatile static LinkedList<Proceso> Todos_Procesos = new LinkedList<Proceso>();
    public volatile static LinkedList<Proceso> Cola_Procesos_Terminados = new LinkedList<Proceso>();
    public volatile static LinkedList<Proceso> Procesos_a_agregar = new LinkedList<Proceso>();

    public volatile static LinkedList<Semaforo> Semaforos = new LinkedList<Semaforo>();
    
    //public volatile static boolean cambio_proceso = false;

    public volatile static int procesos_sincronos=0;
    public volatile static int contador_ticks=0;

    public static int waiting_time = 5;
    
    public CPU(){
        /*
        this.Procesos_por_matar.clear();
        this.Waiting.clear();
        this.Waiting_time.clear();
        this.Waiting_time_cola.clear();
        //this.lista_colas.clear();
        this.Archivos_PID.clear();
        this.Semaforos.clear();
        this.Procesos_a_agregar.clear();
        this.procesos_sincronos=0;
        this.contador_ticks=0;
         */
        Parsear_XML();
    }
    
    /***********************************************
     * Método: run
     * Uso: Método que ejecuta el CPU mientras está
     *      encendido
     *@return void
     ***********************************************/
    public void run() {

        /*
            while(true)
            {
            //actualizar colas y estados puede ser un mismo método
            actualizarColas();
            actualizarEstados();

            process = getProcess();

            //contextSwitch();

            //Puede poner un proceso a terminated o a wait
            exec(process);

            actualizarEstadisticas();
            }
         */
        while(true){
            contador_ticks++;
            Actualizar_Waiting_List();
            //Agregar_procesos();

            //******************************************************************
            int i = get_cola_proceso_a_ejecutar();//obtiene la cola en la que esta el proc
            Proceso proc=null;
            if(i!=-1){
                proc = this.lista_colas.get(i).getFirst();//copia el proceso
            }
            //******************************************************************
            if(proc!=null){
                //**************************************************************
                //ejecutar proceso
                boolean termino = Exec_Proc(proc);
                //System.out.println("Proceso ejecutado: "+proc);
                //**************************************************************
                if(termino){
                    Proceso temp_proc = this.lista_colas.get(proc.cola_en_la_que_esta).poll();
                    Cola_Procesos_Terminados.add(temp_proc);
                    if(temp_proc.sincrono){
                        this.procesos_sincronos--;
                    }
                }
                //**************************************************************
                if(!this.Procesos_por_matar.isEmpty()){
                    Matar_Procesos();
                }
                //**************************************************************
                proc.restar_Quantum();//resta el quantum del proceso
                //**************************************************************
                if(proc.io_waiting && !termino){
                    //MOVER A LA COLA DE WAITING
                    proc.quantum_over=false;
                    //proc.setQuantum_restante(this.lista_colas.get(proc.cola_en_la_que_esta).getQuantum());
                    try{
                        IO_Context_Switch(proc);
                    }catch(Exception exp){
                        System.out.print("");//("Error en el IO context switch");
                    }


                }
                //**************************************************************

                if(proc.quantum_over && !termino){
                    //proc.io_waiting=false;
                    //HACER CONTEXT SWITCH A LA COLA CORRESPONDIENTE
                    try{
                        Quantum_Context_Switch(proc);
                    }catch(Exception exp){
                        System.out.print("");//("Error en el quantum context switch");
                    }
                }
                
            }
            Actualizar_estadisticas();
        }
    }

    /***********************************************
    * Método: Context_Switch
    * Uso: revisa el cambio de proceso
    *@return void
    ***********************************************/
    public void Quantum_Context_Switch(Proceso proc){
        if(proc!=null){
            //this.print("Quantum context switch");//TODO quitar esto
            //proc.Agregar_Waiting_time();
            //proc.quantum_over=false;
            int cola_fuente = proc.cola_en_la_que_esta;
            proc.setEstado(8);//ponerlo en estado de Queue waiting
            //this.print("Cola en la que esta:"+cola_fuente);
            //this.print("Tamaño cola:"+this.lista_colas.size());
            int cola_destino = this.lista_colas.get(cola_fuente).getQueueOnQuantumEnd();
            //**********************************************************************
            //sacar de una cola y meter en la otra
            if(!this.lista_colas.get(cola_fuente).isEmpty()){
                this.lista_colas.get(cola_destino).add(this.lista_colas.get(cola_fuente).poll());
                //this.Waiting.add(this.lista_colas.get(cola_fuente).poll());
                //this.Waiting_time.add(this.waiting_time);
                //this.Waiting_time_cola.add(cola_destino);
            }
                
        }
    }

     /***********************************************
     * Método: Context_Switch
     * Uso: revisa el cambio de proceso
     *@return void
     ***********************************************/
    public void IO_Context_Switch(Proceso proc){
        //this.print("IO context switch");//TODO quitar esto
        //proc.Agregar_Waiting_time();
        //proc.io_waiting=false;
        int cola_fuente = proc.cola_en_la_que_esta;
        //this.print("Cola fuente: "+cola_fuente);
        //**********************************************************************
        //sacar de una cola y meter en la otra
        this.Waiting.add(this.lista_colas.get(cola_fuente).poll());
        this.Waiting_time.add(this.waiting_time);
        this.Waiting_time_cola.add(this.lista_colas.get(cola_fuente).getQueueOnIOEnd());
        //**********************************************************************
    }

    /***********************************************
     * Método: Actualizar_Waiting_List
     * Uso: Actualiza los valores de tiempo de espera
     *      de cada proceso
     *@return void
     ***********************************************/
    public void Actualizar_Waiting_List(){
        //recorre toda la lista de procesos
        for(int i=0;i<this.Waiting.size();i++){

            //System.out.println("blabla");

            Integer a = CPU.Waiting_time.get(i);
            CPU.Waiting_time.remove(i);
            CPU.Waiting_time.add(i, --a);
            //revisar si ya terminó de esperar para sacarlo...
            if(CPU.Waiting_time.get(i)==0){
                //mete en la respectiva cola
                int cola_destino = this.Waiting_time_cola.get(i);
                Proceso proc = this.Waiting.get(i);
                //this.print("Cola destino: "+cola_destino);
                this.lista_colas.get(cola_destino).add(proc);
                //proc.cola_en_la_que_esta=cola_destino;
                //proc.io_waiting=false;
                //proc.setQuantum_restante(this.lista_colas.get(cola_destino).getQuantum());

                proc.io_waiting=false;
                proc.quantum_over=false;

                this.Waiting.remove(i);
                this.Waiting_time.remove(i);
                this.Waiting_time_cola.remove(i);
                i--;
            }
        }

    }

    /***********************************************
     * Método: Actualizar_estadisticas
     * Uso: Actualiza estadíasticas como el turnarround time
     *      y el waiting time... (implícitamente está el
     *      response time)
     *@return void
     ***********************************************/
    public void Actualizar_estadisticas(){

        //**********************************************************************
        //**********************************************************************
        //actualizar el response time...
        for(int i=0;i<this.lista_colas.size();i++){
            for(int j=0;j<this.lista_colas.get(i).getCola().size();j++){
                this.lista_colas.get(i).get(j).Agregar_Response_time();
            }
        }
        //**********************************************************************
        //this.estado=2;//Ready
        //this.estado=3;//Running
        //this.estado=4;//Waiting
        //this.estado=5;//Terminated
        //this.estado=6;//Killed
        //this.estado=7;//In semaphore

        for(int i=0;i<this.Todos_Procesos.size();i++){
            Proceso proc = this.Todos_Procesos.get(i);
            if(proc.getEstado()==2){
                proc.waiting_time++;
            }
            else if(proc.getEstado()==4 || proc.getEstado()==8){
                proc.Agregar_Waiting_time();
            }
            else if(proc.getEstado()==7){
                proc.semaphore_waiting_time++;
            }
        }
    }

    /***********************************************
     * Método: Exec_Proc
     * Uso: ejecuta un proceso de la respectiva cola
     * @param numero_proceso contiene el número de
     *        cola del proceso a ejecutar
     *@return void
     ***********************************************/
    public boolean Exec_Proc(Proceso proc){
        //ejecutar del proceso 1
        //this.print("Ejecutando proceso: "+proc.process_ID);
        return proc.Exec();
    }

     /***********************************************
     * Método: get_proceso_a_ejecutar
     * Uso: Devuelve el proceso a ejecutar
     *@return Proceso con el proceso correspondiente
     ***********************************************/
    public int get_cola_proceso_a_ejecutar() {
        for(int i=0;i<this.lista_colas.size();i++){
            if(!this.lista_colas.get(i).isEmpty()){
                return i;
            }
        }
        return -1;
    }

    /***********************************************
     * Método: contiene_proceso_sincrono
     * Uso: revisa si alguna cola tiene un proceso sincrono
     *@return booleano con la respuesta
     ***********************************************/
    public static boolean contiene_proceso_sincrono(){
        if(CPU.procesos_sincronos>0){
            return true;
        }
        else if(CPU.procesos_sincronos==0){
            return false;
        }
        System.out.println("Clavo al saber si un proceso es síncrono...");
        return false;
        /*
        for(int i=0;i<CPU.lista_colas.size();i++){
            //System.out.println("Cola "+i);
            for(int j=0;j<CPU.lista_colas.get(i).getCola().size();j++){
                //System.out.println("Elemento: "+j);
                if(CPU.lista_colas.get(i).get(j).sincrono){
                    return true;
                }
            }
        }
        return false;
         */
    }

    /***********************************************
     * Método: Matar_Procesos
     * Uso: revisa el cambio de proceso
     *@return void
     ***********************************************/
    public void Matar_Procesos(){

        CPU.Procesos_por_matar.peek().setEstado(6);

        if(CPU.Procesos_por_matar.peek().sincrono){
            this.procesos_sincronos--;
        }

        //System.out.println("si entra hasta acá..");
        //mata el proceso de la cola correspondiente
        for(int i=0;i<this.lista_colas.size();i++){
            if(CPU.lista_colas.get(i).getCola().contains(CPU.Procesos_por_matar.peek())){
                CPU.lista_colas.get(i).remove(CPU.Procesos_por_matar.poll());
            }
        }
    }

    /***********************************************
     * Método: Agregar_Proceso
     * Uso: Agrega un proceso a la cola
     * @param proceso contiene el proceso a agregar
     *@return void
     ***********************************************/
    public static void Agregar_Proceso(Proceso proceso){
        Todos_Procesos.add(proceso);
        //siempre se agrega a la cola 0
        lista_colas.get(0).add(proceso);

        if(proceso.sincrono){
            CPU.procesos_sincronos++;
        }
        /*
        lista_colas.get(0).getLast_in().setEstado(2);//para cambiar a estado ready
        lista_colas.get(0).getLast_in().cola_en_la_que_esta=0;//para saber dentro del proceso en que cola está
        lista_colas.get(0).getLast_in().quantum_restante = CPU.lista_colas.get(0).getQuantum();//asigna el quantum
         */
    }

    /***********************************************
     * Método: Agregar_Procesos
     * Uso: Agrega todos los procesos que esten en la lista. Estos vienen del shell
     *@return void
     ***********************************************/
    public void Agregar_procesos(){
        for(int i=0;i<this.Procesos_a_agregar.size();i++){
            Agregar_Proceso(this.Procesos_a_agregar.get(i));
        }
    }

     /***********************************************
     * Método: Parsear_XML
     * Uso: parsea e inicializa según el contenido del XML
     *@return void
     ***********************************************/
    private void Parsear_XML() {

        try {
            SAXBuilder builder=new SAXBuilder(false);
            //usar el parser Xerces y no queremos
            //que valide el documento
            Document doc=builder.build("/home/martin/Desktop/Sistemas operativos/sys.xml");
            //construyo el arbol en memoria desde el fichero
            // que se lo pasaré por parametro.
            Element raiz=doc.getRootElement();
            //cojo el elemento raiz
            //todos los hijos que tengan como nombre plantilla
            Element cal=raiz.getChild("scheduling");

            int cola_inicial = Integer.parseInt(cal.getAttributeValue("startQueue"));
            System.out.println(" ***************************************");
            System.out.println("Start Queue: "+cola_inicial+"\n");

            try{
                Element path = raiz.getChild("root");
                String root_path = path.getAttributeValue("root_path");
                //System.out.println(root_path);
                Shell.Directorio_Raiz=new File(root_path);
            }catch(Exception exp){
                //System.out.println(Shell.Directorio_Raiz);
            }
            //System.out.println(Shell.Directorio_Raiz.getAbsolutePath());

            List<Element> queue = cal.getChildren("queue");

            //Queue n ***************************************************************************
            for(int i=0;i<queue.size();i++){
                Asignar_datos_cola(queue.get(i),i);
            }
        }catch (Exception e){
            e.printStackTrace();
            System.out.println("Error parseando XML");
        }
        System.out.println(" ***************************************\n");
    }

    public void Asignar_datos_cola(Element e, int i){

        Interfaz_Cola<Proceso> nueva_cola=null;
        String tipo = e.getAttributeValue("type");

        System.out.println("Queue "+(i+1)+": "+tipo);

        //inicializar según el tipo ********************************************
        if(tipo.compareToIgnoreCase("FCFS")==0){
            nueva_cola = new Cola_FCFS();
        }
        else if(tipo.compareToIgnoreCase("Priority")==0){
            nueva_cola = new Cola_Prioridad();
        }
        else if(tipo.compareToIgnoreCase("SJF")==0){
            nueva_cola = new Cola_SJF();
        }
        //**********************************************************************
        //**********************************************************************
        nueva_cola.setNumero_cola(i);
        nueva_cola.setId(Integer.parseInt(e.getAttributeValue("id")));
        nueva_cola.setName(e.getAttributeValue("name"));
        nueva_cola.setType(tipo);
        if(Integer.parseInt(e.getAttributeValue("preemptive"))==1){
            nueva_cola.setPreemptive(true);
        }
        else{
            nueva_cola.setPreemptive(false);
        }
        nueva_cola.setQuantum(Integer.parseInt(e.getAttributeValue("quantum")));
        nueva_cola.setQueueOnIOEnd(Integer.parseInt(e.getAttributeValue("queueOnIOEnd"))-1);
        nueva_cola.setQueueOnQuantumEnd(Integer.parseInt(e.getAttributeValue("queueOnQuantumEnd"))-1);

        this.lista_colas.add(nueva_cola);
    }

    public void print(String cadena){
        System.out.println(cadena);
    }

}
